﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Radiation.Common;

namespace Radiation___IM
{
    public class Server
    {
        private int port = 1456;
        private Thread listener;
        private bool started = false;
        private ManualResetEvent allDone;
        private ClientRegister m_clientRegister;

        public Server()
        {
            listener = new Thread(listenThread);
            allDone = new ManualResetEvent(true);
            m_clientRegister = new ClientRegister();
            ChannelManager.Init();
        }

        /// <summary>
        /// Init the server and start the client listener
        /// </summary>
        /// <returns></returns>
        public rvalue Start()
        {
            if (!started)
            {
                Logger.Init();
                Logger.Instance["error"].ItemAdded += new EventHandler<GenericEventArgs>(Server_ItemAdded);

                Console.WriteLine("Server : requesting start");
                Console.WriteLine("Server : loading user database ...");
                Users.UserManager.Init();
                Users.PermissionGroupManager.Init();
                Data.DatabaseLoader.Load();
                Data.DatabaseWriter.Init();

                Console.WriteLine("Server : initalizing commands ...");
                CommandManager.Init();
                Console.WriteLine("Server : generating unique RSA Key ...");
                RSAManager.Init();
                Console.WriteLine("Server : RSA Generated");
                Console.WriteLine("Server :  -- Now starting listener --");
                listener.Start();
                started = true;
                return rvalue.SERVER_STARTED;
            }
            else
                return rvalue.SERVER_ALREADY_STARTED;
        }

        void Server_ItemAdded(object sender, GenericEventArgs e)
        {
            Console.WriteLine(e["message"]);
        }

        /// <summary>
        /// The function called by a thread to listen for incoming connections
        /// </summary>
        private void listenThread()
        {
            Console.WriteLine("Server : opening socket ...");

            TcpListener server = new TcpListener(IPAddress.Any, Convert.ToInt32(this.port));
            server.Server.NoDelay = true;


            try
            {
                server.Start();

                Console.WriteLine("Server : socket opened. Now listening");

                while (true)
                {
                    allDone.Reset();

                    server.Server.BeginAccept(
                        new AsyncCallback(acceptCallback),
                        server);

                    allDone.WaitOne();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                Console.ReadLine();
            }
        }

        private void acceptCallback(IAsyncResult result)
        {
            Console.WriteLine("Server : client detected.");

            // Get the socket that handles the client request.
            Socket listener = ((TcpListener)result.AsyncState).Server;
            Socket handler = listener.EndAccept(result);
            handler.NoDelay = true;

            // Signal the main thread to continue.
            allDone.Set();
            Client client = new Client(handler);
            client.setKey(m_clientRegister.register(client));
            client.Nickname = "guest" + Convert.ToString(client.Key);
            
            // key exchange
            // ------------

            // RSA
            client.Handler.Send(UnicodeEncoding.Unicode.GetBytes(RSAManager.Instance.XMLPublicKey));
            byte[] buffer = new byte[65535];
            int read = client.Handler.Receive(buffer);
            client.PublicKeyXML = UnicodeEncoding.Unicode.GetString(buffer, 0, read);

            // AES Key (with RSA)
            client.Handler.Send(UnicodeEncoding.Unicode.GetBytes(RSAManager.Instance.Encrypt("aes.getkey", client)));
            read = client.Handler.Receive(buffer);
            client.AESProvider.Key = Convert.FromBase64String(RSAManager.Instance.Decrypt(UnicodeEncoding.Unicode.GetString(buffer, 0, read)));

            // AES IV (with RSA)
            client.Handler.Send(UnicodeEncoding.Unicode.GetBytes(RSAManager.Instance.Encrypt("aes.getiv", client)));
            read = client.Handler.Receive(buffer);
            client.AESProvider.IV = Convert.FromBase64String(RSAManager.Instance.Decrypt(UnicodeEncoding.Unicode.GetString(buffer, 0, read)));

            // ------------
            // end key exchange

            ChannelManager.Instance["default"].registerClient(client); 
            Thread.Sleep(5); 
            MessageExecution.sendDataAPI(client, client.Nickname, "user.nickname");

            client.Handler.BeginReceive(client.Buffer, 0, client.BufferSize, SocketFlags.None,
                new AsyncCallback(readCallback), client);
        }

        public static void readCallback(IAsyncResult ar)
        {
            try
            {
                Client client = (Client)ar.AsyncState;

                // Read data from the client socket.
                int read = client.Handler.EndReceive(ar);

                // Data was read from the client socket.
                if (read > 0)
                {
                    string[] commands = client.AESProvider.Decrypt(UnicodeEncoding.Unicode.GetString(client.Buffer, 0, read));

                    for (int i = 0; i < commands.Length; i++)
                    {
                        MessageParser parser = new MessageParser();

                        if (parser.parse(commands[i]) == rvalue.MESSAGE_PARSED)
                            MessageExecution.Execute(parser, client);
                    }
                }

                client.Handler.BeginReceive(client.Buffer, 0, client.BufferSize, SocketFlags.None,
                    new AsyncCallback(readCallback), client);
            }
            catch (Exception e)
            {
                Console.WriteLine("Server : client : error occured");
                ChannelManager.Instance["default"].unregisterClient((Client)ar.AsyncState);
                Console.WriteLine(e.ToString());
            }
        }
    }
}
